#include "core\Graphics.h"

#include <windows.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include <fstream>
#include <vector>
#include <boost\filesystem.hpp>
#include "common\Debug.h"
using namespace boost::filesystem;

Graphics::Graphics(StateManager* manager) : GameState(manager)
{
}

Graphics* Graphics::getInstance(StateManager* manager)
{
  static Graphics instance(manager);
  return &instance;
}

void Graphics::enterState()
{
  if(!init()) debug_printf("graphics init() failed.\n");
}

void Graphics::exitState()
{
  deinit();
}

bool Graphics::loadTextures()
{
	std::string dir("res");
	path p(dir);
	try
	{
		if(exists(p))
		{
			typedef std::vector<path> vec;
			vec textureFiles;
			copy(recursive_directory_iterator(p), recursive_directory_iterator(), back_inserter(textureFiles));
			for(vec::const_iterator it(textureFiles.begin()); it != textureFiles.end(); ++it)
			{
				if(is_directory(*it))
					continue;

				if((*it).string().find(".svn") != std::string::npos)
					continue;

				glTexture tex;
				m_textureManager.loadTextureFromDisk((*it).string().c_str(), &tex);
				std::string filename = (*it).relative_path().string();
				std::string filenameKey = filename.substr(dir.length() + 1, filename.length()); // +1 to also chop off the slash
				textureMap[filenameKey] = tex;
			}
		}
		else
		{
			debug_printf("%s does not exist", p.string().c_str());
			return false;
		}
	}
	catch (const filesystem_error& ex)
	{
		debug_printf("%s\n", ex.what());
		return false;
	}

	return true;
}

bool Graphics::init()
{	
  for(int i=0;i<256;i++) m_maps[i] = 0;
  setMap(0);
  m_player.moveTo(m_currentMap->getStairsUpPos());
  m_Camera.posx = m_player.getPosition().x;
  m_Camera.posy = m_player.getPosition().y;
  m_timeDelta = 0.0;

  m_textBox.setWidth(28);
  m_textBox.setHeight(8);
  
  m_fontManager = FontManager::getInstance();

  m_fontManager->loadFont("cn12", "Courier New", 12, false, false, false);
  m_fontManager->getInstance()->loadFont("sp24", "Segoe Print", 24, false, false, false);
	if(!loadTextures()) return false;
	// OpenGL init
	glClearColor(0.f,0.f,0.f,0.f);
	glClearDepth(1.f);
	glDepthFunc(GL_LEQUAL);
	glEnable(GL_DEPTH_TEST);
	glShadeModel(GL_SMOOTH);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);
	glEnable(GL_LIGHTING);
	GLfloat lightAmbient[]={0.2f,0.2f,0.2f,1.f};
	GLfloat lightDiffuse[]={0.8f,0.8f,0.8f,1.f};
	GLfloat lightPos[]={0.f,0.f,-5.f,1.f};
	glLightfv(GL_LIGHT1,GL_AMBIENT,lightAmbient);
	glLightfv(GL_LIGHT1,GL_DIFFUSE,lightDiffuse);
	glLightfv(GL_LIGHT1,GL_POSITION,lightPos);
	glEnable(GL_LIGHT1);
	//glEnable(GL_COLOR_MATERIAL);
	glEnable(GL_TEXTURE_2D);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);
	return true;
}

void Graphics::deinit()
{
  for(int i=0;i<256;i++)
  {
    if(m_maps[i] != 0) delete m_maps[i];
    m_maps[i] = 0;
  }
	for(texMap::const_iterator it(textureMap.begin()); it != textureMap.end(); ++it)
	{
		glTexture tex = it->second;
		m_textureManager.freeTexture(&tex);
	}
}

void Graphics::draw()
{
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

  glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
  gluPerspective(80.f,(GLfloat)800/(GLfloat)600,1.f,1000.f);	// FOV, aspect ratio, near-, far-clipping
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
	gluLookAt(m_Camera.posx,m_Camera.posy,m_Camera.posz,
		m_Camera.posx+m_Camera.anglex,m_Camera.posy+m_Camera.angley,0.f,0.f,1.f,0.f);
  
	drawMap();  

  glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
  gluOrtho2D(-1,1,-1,1);  
  glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
  glDisable(GL_LIGHTING);
  glClear(GL_DEPTH_BUFFER_BIT);

  drawOverlay();

  glEnable(GL_LIGHTING);

	glFlush();
}

void Graphics::drawMap()
{
  if(m_currentMap == 0) return;
  glPushMatrix();
  glTranslatef(0.f, 0.f, -10.f);  
  for(int y=0;y<m_currentMap->getHeight();y++)
  {
    glPushMatrix();
    for(int x=0;x<m_currentMap->getWidth();x++)
    {
      if(m_currentMap->getTileType(Vec(x,y)) == SOLID) glBindTexture(GL_TEXTURE_2D,textureMap["solid.bmp"].textureID);
      else if(m_currentMap->getTileType(Vec(x,y)) == DOOR) glBindTexture(GL_TEXTURE_2D,textureMap["door.bmp"].textureID);
      else if(m_currentMap->getTileType(Vec(x,y)) == STAIRS_DOWN) glBindTexture(GL_TEXTURE_2D,textureMap["stairs_down.bmp"].textureID);
      else if(m_currentMap->getTileType(Vec(x,y)) == STAIRS_UP) glBindTexture(GL_TEXTURE_2D,textureMap["stairs_up.bmp"].textureID);
      else glBindTexture(GL_TEXTURE_2D,textureMap["floor.bmp"].textureID);
      glBegin(GL_QUADS);
        glNormal3f(0.f,0.f,1.f);
        glTexCoord2d(0.f,0.f); glVertex3f(0,0,0);
        glTexCoord2d(1.f,0.f); glVertex3f(1,0,0);
        glTexCoord2d(1.f,1.f); glVertex3f(1,1,0);
        glTexCoord2d(0.f,1.f); glVertex3f(0,1,0);
      glEnd();
      glTranslatef(1.f, 0.f, 0.f);
    }  
    glPopMatrix();
    glTranslatef(0.f, 1.f, 0.f);
  } 
  glPopMatrix();

  Vec playerPos = m_player.getPosition();
  glTranslatef((float)playerPos.x, (float)playerPos.y, -10.f);
  glBindTexture(GL_TEXTURE_2D,textureMap["player.tga"].textureID);
  glBegin(GL_QUADS);
      glNormal3f(0.f,0.f,1.f);
      glTexCoord2d(0.f,0.f); glVertex3f(0,0,0);
      glTexCoord2d(1.f,0.f); glVertex3f(1,0,0);
      glTexCoord2d(1.f,1.f); glVertex3f(1,1,0);
      glTexCoord2d(0.f,1.f); glVertex3f(0,1,0);
   glEnd();   
}

void Graphics::picking()
{
  if(m_currentMap == 0) return;

  glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
  gluPerspective(80.f,(GLfloat)800/(GLfloat)600,1.f,1000.f);	// FOV, aspect ratio, near-, far-clipping
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
	gluLookAt(m_Camera.posx,m_Camera.posy,m_Camera.posz,
		m_Camera.posx+m_Camera.anglex,m_Camera.posy+m_Camera.angley,0.f,0.f,1.f,0.f);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glDisable(GL_TEXTURE_2D);
  glDisable(GL_LIGHTING);
  glDisable(GL_BLEND);

  glPushMatrix();
  glTranslatef(0.f, 0.f, -10.f);  
  for(int y=0;y<m_currentMap->getHeight();y++)
  {
    glPushMatrix();
    for(int x=0;x<m_currentMap->getWidth();x++)
    {
      glColor3ub(m_currentMap->getTile(Vec(x,y))->getColorID(0), m_currentMap->getTile(Vec(x,y))->getColorID(1), m_currentMap->getTile(Vec(x,y))->getColorID(2));
      glBegin(GL_QUADS);
        glVertex3f(0,0,0);
        glVertex3f(1,0,0);
        glVertex3f(1,1,0);
        glVertex3f(0,1,0);
      glEnd();
      glTranslatef(1.f, 0.f, 0.f);
    }  
    glPopMatrix();
    glTranslatef(0.f, 1.f, 0.f);
  }
  glPopMatrix();

  glEnable(GL_TEXTURE_2D);
  glEnable(GL_LIGHTING);
  glEnable(GL_BLEND);
}

void Graphics::drawOverlay()
{
  glBindTexture(GL_TEXTURE_2D,textureMap["overlay.tga"].textureID);
  glBegin(GL_QUADS);    
    glColor4f(1.f,1.f,1.f,1.f);
    glNormal3f(0.f,0.f,1.f);
    glTexCoord2d(0.f ,0.f); glVertex3f(-1.f, -1.f, -0.1f);
    glTexCoord2d(1.f, 0.f); glVertex3f(-0.4f, -1.f, -0.1f);
    glTexCoord2d(1.f, 1.f); glVertex3f(-0.4f, -0.5f, -0.1f);
    glTexCoord2d(0.f, 1.f); glVertex3f(-1.f, -0.5f, -0.1f);
  glEnd();
  
  glDisable(GL_TEXTURE_2D);
  glColor4f(1.f,0.f,0.f,1.f);
  glRasterPos2f(-0.1f, -0.8f);
  m_fontManager->print("cn12", "Camera x:%.1f y:%.1f z:%.1f", m_Camera.posx, m_Camera.posy, m_Camera.posz);
  glColor4f(1.f,1.f,1.f,1.f);
  glRasterPos2f(-0.9f, -0.6f);
  //m_fontManager.print("cn12", "Mouse x:%d y:%d", x, y);
  glColor4f(1.f,0.f,0.f,1.f);
  glRasterPos2f(-0.3f, -0.4f);
  m_fontManager->print("sp24", "HALLO WELT   (Segoe Print)");

  for(int i=0;i<m_textBox.getHeight();i++)
  {
    glRasterPos2f(-0.95f, -0.59f - i * 0.05f);
    m_fontManager->print("cn12", m_textBox.getLine(i));
  }

  glEnable(GL_TEXTURE_2D);
}

void Graphics::setMap(int level)
{
  if(level < 0 || level > 255) return;
  m_currentLevel = level;
  if(m_maps[m_currentLevel] == 0)
  {
    m_maps[m_currentLevel] = new Map(RNG.getInt(30,80), RNG.getInt(30,80));
    if(RNG.getInt(0,100) < 75) m_mapGenerator.generateCaveMap(m_maps[m_currentLevel]);
    else m_mapGenerator.generateDungeonMap(m_maps[m_currentLevel]);
  }
  m_currentMap = m_maps[m_currentLevel];  
}

APP_MSG Graphics::update(DWORD milliseconds, Keys* keys, Mouse* mouse)
{
  double speedFactor=milliseconds/16.6f;
  //// TODO: framerate capping
  ////if(milliseconds<16) Sleep(16-milliseconds);  

  if(keys->isPressed(VK_ESCAPE)) return MSG_QUIT;
  if(keys->isPressed(VK_F1)) return MSG_TOGGLE_FULLSCREEN;

  keyPress(*keys, speedFactor);

  ///*
  //if(m_keys.isPressed(VK_PRIOR)) m_graphics.m_Camera.zoom(-1.f*speedFactor);
  //if(m_keys.isPressed(VK_NEXT)) m_graphics.m_Camera.zoom(1.f*speedFactor);
  //if(!m_keys.isPressed(VK_SHIFT)&&m_keys.isPressed(VK_LEFT)) m_graphics.m_Camera.hori(-1.f*speedFactor);
  //if(!m_keys.isPressed(VK_SHIFT)&&m_keys.isPressed(VK_RIGHT)) m_graphics.m_Camera.hori(1.f*speedFactor);
  //if(!m_keys.isPressed(VK_SHIFT)&&m_keys.isPressed(VK_UP)) m_graphics.m_Camera.vert(1.f*speedFactor);
  //if(!m_keys.isPressed(VK_SHIFT)&&m_keys.isPressed(VK_DOWN)) m_graphics.m_Camera.vert(-1.f*speedFactor);
  //if(m_keys.isPressed(VK_SHIFT)&&m_keys.isPressed(VK_LEFT)) m_graphics.m_Camera.lookx(-1.f*speedFactor);
  //if(m_keys.isPressed(VK_SHIFT)&&m_keys.isPressed(VK_RIGHT)) m_graphics.m_Camera.lookx(1.f*speedFactor);
  //if(m_keys.isPressed(VK_SHIFT)&&m_keys.isPressed(VK_UP)) m_graphics.m_Camera.looky(1.f*speedFactor);
  //if(m_keys.isPressed(VK_SHIFT)&&m_keys.isPressed(VK_DOWN)) m_graphics.m_Camera.looky(-1.f*speedFactor);*/

  if(mouse->isClick(0))
  {
    // left mouse button event
    click(mouse->getClickX(), mouse->getClickY());
    mouse->doneClick(0);
  }
  if(mouse->isClick(1))
  {
    // right mouse button event
    mouse->doneClick(1);
  }
  return MSG_NONE;
}

void Graphics::click(unsigned int x, unsigned int y)
{
  m_textBox.addMessage("Clicked on: (%d,%d)",x,y);
  picking();
  unsigned char pixel[3]; 
  GLint viewport[4];
  glGetIntegerv(GL_VIEWPORT, viewport);
  glReadPixels(x, viewport[3] - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, pixel);
  for(int x=0;x<m_currentMap->getWidth();x++) for(int y=0;y<m_currentMap->getHeight();y++)
  {
    if(m_currentMap->getTile(Vec(x,y))->isID(pixel))
    {            
      //TODO:
      Path p = m_currentMap->path(m_player.getPosition(), Vec(x,y));      
      if(p.size() > 1) m_player.moveTo(p[1]);
    }
  }
}

void Graphics::keyPress(Keys keys, double speedFactor)
{    
  if(!keys.isPressed(VK_SHIFT) && keys.isPressed(VK_LEFT)) m_Camera.hori(-1.f*speedFactor, 0, m_currentMap->getWidth());
	if(!keys.isPressed(VK_SHIFT) && keys.isPressed(VK_RIGHT)) m_Camera.hori(1.f*speedFactor, 0, m_currentMap->getWidth());
	if(!keys.isPressed(VK_SHIFT) && keys.isPressed(VK_UP)) m_Camera.vert(1.f*speedFactor, 0, m_currentMap->getHeight());
	if(!keys.isPressed(VK_SHIFT) && keys.isPressed(VK_DOWN)) m_Camera.vert(-1.f*speedFactor, 0, m_currentMap->getHeight());

  m_timeDelta += speedFactor;
  if(m_timeDelta < 4.0) return;
  m_timeDelta = 0;
  Vec dir(0,0);
  if(keys.isPressed(VK_NUMPAD1)) dir = Vec(-1,-1);
  if(keys.isPressed(VK_NUMPAD2)) dir = Vec(0,-1);
  if(keys.isPressed(VK_NUMPAD3)) dir = Vec(1,-1);
  if(keys.isPressed(VK_NUMPAD4)) dir = Vec(-1,0);
  if(keys.isPressed(VK_NUMPAD5)) dir = Vec(0,0);
  if(keys.isPressed(VK_NUMPAD6)) dir = Vec(1,0);
  if(keys.isPressed(VK_NUMPAD7)) dir = Vec(-1,1);
  if(keys.isPressed(VK_NUMPAD8)) dir = Vec(0,1);
  if(keys.isPressed(VK_NUMPAD9)) dir = Vec(1,1);
  Vec pos = m_player.getPosition() + dir;
  if(dir != Vec(0,0))
  {
    if(m_currentMap->isPassable(pos)) m_player.moveTo(pos);
    if(m_currentMap->getTileType(pos) == STAIRS_DOWN)
    {
      setMap(m_currentLevel + 1);
      m_player.moveTo(m_currentMap->getStairsUpPos());
      m_Camera.posx = m_player.getPosition().x;
      m_Camera.posy = m_player.getPosition().y;
    }
    if(m_currentMap->getTileType(pos) == STAIRS_UP)
    {
      setMap(m_currentLevel - 1);
      m_player.moveTo(m_currentMap->getStairsDownPos());
      m_Camera.posx = m_player.getPosition().x;
      m_Camera.posy = m_player.getPosition().y;
    }
  }
}

Camera::Camera()
{
	posx=0.f;
	posy=0.f;
	posz=15.f;
	anglex=0.f;
	angley=0.f;
}

void Camera::zoom(double z, double clampMin, double clampMax)
{
	posz+=z;
  if(posz < clampMin) posz = clampMin;
  if(posz > clampMax) posz = clampMax;
}

void Camera::vert(double v, double clampMin, double clampMax)
{
	posy+=v;
	if(posy < clampMin) posy = clampMin;
  if(posy > clampMax) posy = clampMax;
}

void Camera::hori(double h, double clampMin, double clampMax)
{
	posx+=h;
	if(posx < clampMin) posx = clampMin;
  if(posx > clampMax) posx = clampMax;
}

void Camera::lookx(double look, double clampMin, double clampMax)
{
	anglex+=look;
	if(anglex < clampMin) anglex = clampMin;
  if(anglex > clampMax) anglex = clampMax;
}

void Camera::looky(double look, double clampMin, double clampMax)
{
	angley+=look;
	if(angley < clampMin) angley = clampMin;
  if(angley > clampMax) angley = clampMax;
}